﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Gunningine
{
    using Boo.Lang.Compiler;
    using Boo.Lang.Compiler.IO;
    using Boo.Lang.Compiler.Pipelines;
    using System.Reflection;

    public class ScriptManager : IDisposable
    {
        Dictionary<string, Type> scriptTypes = new Dictionary<string, Type>();

        Dictionary<string, Type> selfRegisteringTypes = new Dictionary<string, Type>();

        /// <summary>
        /// Constructor.
        /// </summary>
        public ScriptManager()
        {
        }

        /// <summary>
        /// Compile several scripts together.
        /// </summary>
        /// <param name="scripts"></param>
        public void CompileScripts(TextResource[] scripts)
        {
            BooCompiler compiler = new BooCompiler();

            foreach (var script in scripts)
            {
                compiler.Parameters.Input.Add(new StringInput(script.Filename, script.Text));
            }

            compiler.Parameters.Pipeline = new CompileToMemory();
            compiler.Parameters.Ducky = true;
            compiler.Parameters.LibPaths.Add("Plugins");

            CompilerContext context = compiler.Run();

            if (context.GeneratedAssembly != null)
            {
                Type[] types = context.GeneratedAssembly.GetTypes();

                foreach (var ttype in types)
                {
                    this.scriptTypes[ttype.Name] = ttype;

                    Logger.Debug(ttype.Name);
                    foreach (var mi in ttype.GetMethods())
                    {
                        Logger.Debug(mi.Name);
                    }
                }

                // Find self-registering objects and add them to factories.
                var selfRegisterTypes = types.Where(p => p.GetCustomAttributes(typeof(SelfRegisterObject), true).Length == 1);
                foreach (var srt in selfRegisterTypes)
                {
                    Logger.Debug("Self registering type: " + srt.Name);
                    this.selfRegisteringTypes[srt.Name] = srt;
                }
            }
            else
            {
                var errorList = context.Errors.Cast<CompilerError>();
                var errorMsgs = errorList.Select(p => p.ToString());

                throw new ScriptCompilationException("Files", errorMsgs.ToArray());
            }
        }

        /// <summary>
        /// Compile a script.
        /// </summary>
        /// <param name="script"></param>
        public void CompileScript(TextResource script)
        {
            CompileScripts(new TextResource[] { script });
        }

        /// <summary>
        /// Get a list of types that are defined in loaded script assemblies, so
        /// we can instantiate them.
        /// </summary>
        /// <param name="baseType"></param>
        /// <returns></returns>
        public List<Type> GetAssignableScriptTypes(Type baseType)
        {
            return this.scriptTypes.Values.Where(t => baseType.IsAssignableFrom(t)).ToList();
        }

        /// <summary>
        /// Create a scripted type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public T CreateType<T>(string typeName, params object[] args)
        {
            if (this.scriptTypes.ContainsKey(typeName))
            {
                return (T)Activator.CreateInstance(this.scriptTypes[typeName], args);
            }
            else
            {
                throw new Exception("Could not find scripted type: " + typeName);
            }
        }

        /// <summary>
        /// Create a self-registered type.
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public T CreateSelfRegisteredType<T>(string typeName, params object[] args)
        {
            if (this.scriptTypes.ContainsKey(typeName))
            {
                return (T)Activator.CreateInstance(this.selfRegisteringTypes[typeName], args);
            }
            else
            {
                throw new Exception("Could not find scripted type: " + typeName);
            }
        }

        #region IDisposable implementation

        bool _disposed = false;

        public void Dispose()
        {
            this._disposed = true;
        }

        #endregion

    }
}
